// Copyright (c) 2018 The VeChainThor developers

// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>

package runtime_test

import (
	"encoding/hex"
	"fmt"
	"math"
	"math/big"
	"testing"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/stretchr/testify/assert"

	"github.com/vechain/thor/v2/abi"
	"github.com/vechain/thor/v2/block"
	"github.com/vechain/thor/v2/builtin"
	"github.com/vechain/thor/v2/chain"
	"github.com/vechain/thor/v2/genesis"
	"github.com/vechain/thor/v2/muxdb"
	"github.com/vechain/thor/v2/runtime"
	"github.com/vechain/thor/v2/state"
	"github.com/vechain/thor/v2/thor"
	"github.com/vechain/thor/v2/trie"
	"github.com/vechain/thor/v2/tx"
	"github.com/vechain/thor/v2/vm"
	"github.com/vechain/thor/v2/xenv"
)

func M(a ...any) []any {
	return a
}

func TestEVMFunction(t *testing.T) {
	target := thor.BytesToAddress([]byte("acc01"))

	type context struct {
		chain  *chain.Chain
		state  *state.State
		method *abi.Method
	}

	type testcase = struct {
		name       string
		code       string
		abi        string
		methodName string
		testFunc   func(*context, *testing.T)
	}

	baseTests := []testcase{
		{
			name:       "Contract Suicide",
			code:       "608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063085da1b3146044575b600080fd5b348015604f57600080fd5b5060566058565b005b3373ffffffffffffffffffffffffffffffffffffffff16ff00a165627a7a723058204cb70b653a3d1821e00e6ade869638e80fa99719931c9fa045cec2189d94086f0029",
			abi:        `[{"constant":false,"inputs":[],"name":"testSuicide","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]`,
			methodName: "testSuicide",
			testFunc: func(ctx *context, t *testing.T) {
				// contract:
				//
				// pragma solidity ^0.4.18;

				// contract TestSuicide {
				// 	function testSuicide() public {
				// 		selfdestruct(msg.sender);
				// 	}
				// }

				// time := ctx.chain.GetBlockSummary()
				// .Header().Timestamp()

				head, _ := ctx.chain.GetBlockSummary(0)
				time := head.Header.Timestamp()

				ctx.state.SetEnergy(target, big.NewInt(100), time)
				ctx.state.SetBalance(target, big.NewInt(200))

				methodData, err := ctx.method.EncodeInput()
				if err != nil {
					t.Fatal(err)
				}

				origin := genesis.DevAccounts()[0].Address
				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{Time: time}, &thor.NoFork).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{Origin: origin})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				expectedTransfer := &tx.Transfer{
					Sender:    target,
					Recipient: origin,
					Amount:    big.NewInt(200),
				}
				assert.Equal(t, 1, len(out.Transfers))
				assert.Equal(t, expectedTransfer, out.Transfers[0])

				event, _ := builtin.Energy.ABI.EventByName("Transfer")
				expectedEvent := &tx.Event{
					Address: builtin.Energy.Address,
					Topics:  []thor.Bytes32{event.ID(), thor.BytesToBytes32(target.Bytes()), thor.BytesToBytes32(origin.Bytes())},
					Data:    []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100},
				}
				assert.Equal(t, 1, len(out.Events))
				assert.Equal(t, expectedEvent, out.Events[0])

				assert.Equal(t, M(big.NewInt(0), nil), M(ctx.state.GetBalance(target)))
				assert.Equal(t, M(big.NewInt(0), nil), M(builtin.Energy.Native(ctx.state, time).Get(target)))

				bal, _ := new(big.Int).SetString("1000000000000000000000000000", 10)
				assert.Equal(t, M(new(big.Int).Add(bal, big.NewInt(200)), nil), M(ctx.state.GetBalance(origin)))
				assert.Equal(t, M(new(big.Int).Add(bal, big.NewInt(100)), nil), M(builtin.Energy.Native(ctx.state, time).Get(origin)))
			},
		},
		{
			name:       "ChainID",
			code:       "6080604052348015600f57600080fd5b506004361060285760003560e01c8063adc879e914602d575b600080fd5b60336047565b604051603e9190605c565b60405180910390f35b600046905090565b6056816075565b82525050565b6000602082019050606f6000830184604f565b92915050565b600081905091905056fea264697066735822122060b67d944ffa8f0c5ee69f2f47decc3dc175ea2e4341a4de3705d72b868ce2b864736f6c63430008010033",
			abi:        `[{"inputs":[],"name":"chainID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`,
			methodName: "chainID",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract TestChainID {

				//     function chainID() public view returns (uint256) {
				//         return block.chainid;
				//     }
				// }

				methodData, err := ctx.method.EncodeInput()
				if err != nil {
					t.Fatal(err)
				}

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				assert.Equal(t, ctx.chain.GenesisID(), thor.BytesToBytes32(out.Data))
			},
		},
		{
			name:       "Self Balance",
			code:       "6080604052348015600f57600080fd5b506004361060285760003560e01c8063b0bed0ba14602d575b600080fd5b60336047565b604051603e9190605c565b60405180910390f35b600047905090565b6056816075565b82525050565b6000602082019050606f6000830184604f565b92915050565b600081905091905056fea2646970667358221220eeac1b7322c414db88987af09d3c8bdfde83bb378be9ac0e9ebe3fe34ecbcf2564736f6c63430008010033",
			abi:        `[{"inputs":[],"name":"selfBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`,
			methodName: "selfBalance",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract TestSelfBalance {

				//     function selfBalance() public view returns (uint256) {
				//         return address(this).balance;
				//     }
				// }

				ctx.state.SetBalance(target, big.NewInt(100))

				methodData, err := ctx.method.EncodeInput()
				if err != nil {
					t.Fatal(err)
				}

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				assert.True(t, new(big.Int).SetBytes(out.Data).Cmp(big.NewInt(100)) == 0)
			},
		},
		{
			name:       "Blake2F",
			code:       "608060405234801561001057600080fd5b50600436106100365760003560e01c806372de3cbd1461003b578063fc75ac471461006b575b600080fd5b61005560048036038101906100509190610894565b610089565b6040516100629190610a9b565b60405180910390f35b6100736102e5565b6040516100809190610a9b565b60405180910390f35b61009161063c565b61009961063c565b600087876000600281106100d6577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002015188600160028110610115577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002015188600060048110610154577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002015189600160048110610193577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200201518a6002600481106101d2577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200201518b600360048110610211577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200201518b600060028110610250577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200201518c60016002811061028f577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200201518c6040516020016102ae9a999897969594939291906109e7565b604051602081830303815290604052905060408260d5602084016009600019fa6102d757600080fd5b819250505095945050505050565b6102ed61063c565b6000600c90506102fb61063c565b7f48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa581600060028110610356577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020020181815250507fd182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b816001600281106103ba577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020020181815250506103cb61065e565b7f616263000000000000000000000000000000000000000000000000000000000081600060048110610426577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200201818152505060008160016004811061046b577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020020181815250506000816002600481106104b0577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020020181815250506000816003600481106104f5577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002018181525050610506610680565b7f030000000000000000000000000000000000000000000000000000000000000081600060028110610561577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002019077ffffffffffffffffffffffffffffffffffffffffffffffff1916908177ffffffffffffffffffffffffffffffffffffffffffffffff1916815250506000816001600281106105de577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002019077ffffffffffffffffffffffffffffffffffffffffffffffff1916908177ffffffffffffffffffffffffffffffffffffffffffffffff1916815250506000600190506106328585858585610089565b9550505050505090565b6040518060400160405280600290602082028036833780820191505090505090565b6040518060800160405280600490602082028036833780820191505090505090565b6040518060400160405280600290602082028036833780820191505090505090565b60006106b56106b084610adb565b610ab6565b905080828560208602820111156106cb57600080fd5b60005b858110156106fb57816106e18882610855565b8452602084019350602083019250506001810190506106ce565b5050509392505050565b600061071861071384610b01565b610ab6565b9050808285602086028201111561072e57600080fd5b60005b8581101561075e57816107448882610855565b845260208401935060208301925050600181019050610731565b5050509392505050565b600061077b61077684610b27565b610ab6565b9050808285602086028201111561079157600080fd5b60005b858110156107c157816107a7888261086a565b845260208401935060208301925050600181019050610794565b5050509392505050565b600082601f8301126107dc57600080fd5b60026107e98482856106a2565b91505092915050565b600082601f83011261080357600080fd5b6004610810848285610705565b91505092915050565b600082601f83011261082a57600080fd5b6002610837848285610768565b91505092915050565b60008135905061084f81610ca1565b92915050565b60008135905061086481610cb8565b92915050565b60008135905061087981610ccf565b92915050565b60008135905061088e81610ce6565b92915050565b600080600080600061014086880312156108ad57600080fd5b60006108bb8882890161087f565b95505060206108cc888289016107cb565b94505060606108dd888289016107f2565b93505060e06108ee88828901610819565b92505061012061090088828901610840565b9150509295509295909350565b60006109198383610993565b60208301905092915050565b61092e81610b57565b6109388184610b6f565b925061094382610b4d565b8060005b8381101561097457815161095b878261090d565b965061096683610b62565b925050600181019050610947565b505050505050565b61098d61098882610b7a565b610bfd565b82525050565b61099c81610b86565b82525050565b6109b36109ae82610b86565b610c0f565b82525050565b6109ca6109c582610b90565b610c19565b82525050565b6109e16109dc82610bbc565b610c23565b82525050565b60006109f3828d6109d0565b600482019150610a03828c6109a2565b602082019150610a13828b6109a2565b602082019150610a23828a6109a2565b602082019150610a3382896109a2565b602082019150610a4382886109a2565b602082019150610a5382876109a2565b602082019150610a6382866109b9565b600882019150610a7382856109b9565b600882019150610a83828461097c565b6001820191508190509b9a5050505050505050505050565b6000604082019050610ab06000830184610925565b92915050565b6000610ac0610ad1565b9050610acc8282610bcc565b919050565b6000604051905090565b600067ffffffffffffffff821115610af657610af5610c47565b5b602082029050919050565b600067ffffffffffffffff821115610b1c57610b1b610c47565b5b602082029050919050565b600067ffffffffffffffff821115610b4257610b41610c47565b5b602082029050919050565b6000819050919050565b600060029050919050565b6000602082019050919050565b600081905092915050565b60008115159050919050565b6000819050919050565b60007fffffffffffffffff00000000000000000000000000000000000000000000000082169050919050565b600063ffffffff82169050919050565b610bd582610c76565b810181811067ffffffffffffffff82111715610bf457610bf3610c47565b5b80604052505050565b6000610c0882610c35565b9050919050565b6000819050919050565b6000819050919050565b6000610c2e82610c87565b9050919050565b6000610c4082610c94565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b60008160e01b9050919050565b60008160f81b9050919050565b610caa81610b7a565b8114610cb557600080fd5b50565b610cc181610b86565b8114610ccc57600080fd5b50565b610cd881610b90565b8114610ce357600080fd5b50565b610cef81610bbc565b8114610cfa57600080fd5b5056fea2646970667358221220d54d4583b224c049d80665ae690afd0e7e998bf883c6b97472d292d1e2e5fa3e64736f6c63430008010033",
			abi:        `[{"inputs":[{"internalType":"uint32","name":"rounds","type":"uint32"},{"internalType":"bytes32[2]","name":"h","type":"bytes32[2]"},{"internalType":"bytes32[4]","name":"m","type":"bytes32[4]"},{"internalType":"bytes8[2]","name":"t","type":"bytes8[2]"},{"internalType":"bool","name":"f","type":"bool"}],"name":"F","outputs":[{"internalType":"bytes32[2]","name":"","type":"bytes32[2]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callF","outputs":[{"internalType":"bytes32[2]","name":"","type":"bytes32[2]"}],"stateMutability":"view","type":"function"}]`,
			methodName: "callF",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract TestBlake2 {
				// 	function F(uint32 rounds, bytes32[2] memory h, bytes32[4] memory m, bytes8[2] memory t, bool f) public view returns (bytes32[2] memory) {
				// 		bytes32[2] memory output;

				// 		bytes memory args = abi.encodePacked(rounds, h[0], h[1], m[0], m[1], m[2], m[3], t[0], t[1], f);

				// 		assembly {
				// 		  if iszero(staticcall(not(0), 0x09, add(args, 32), 0xd5, output, 0x40)) {
				// 			revert(0, 0)
				// 		  }
				// 		}

				// 		return output;
				// 	  }

				// 	  function callF() public view returns (bytes32[2] memory) {
				// 		uint32 rounds = 12;

				// 		bytes32[2] memory h;
				// 		h[0] = hex"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5";
				// 		h[1] = hex"d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b";

				// 		bytes32[4] memory m;
				// 		m[0] = hex"6162630000000000000000000000000000000000000000000000000000000000";
				// 		m[1] = hex"0000000000000000000000000000000000000000000000000000000000000000";
				// 		m[2] = hex"0000000000000000000000000000000000000000000000000000000000000000";
				// 		m[3] = hex"0000000000000000000000000000000000000000000000000000000000000000";

				// 		bytes8[2] memory t;
				// 		t[0] = hex"03000000";
				// 		t[1] = hex"00000000";

				// 		bool f = true;

				// 		// Expected output:
				// 		// ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1
				// 		// 7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923
				// 		return F(rounds, h, m, t, f);
				// 	  }
				//   }

				methodData, err := ctx.method.EncodeInput()
				if err != nil {
					t.Fatal(err)
				}

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				var hashes [2][32]uint8
				ctx.method.DecodeOutput(out.Data, &hashes)

				assert.Equal(t, thor.MustParseBytes32("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1"), thor.Bytes32(hashes[0]))
				assert.Equal(t, thor.MustParseBytes32("7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"), thor.Bytes32(hashes[1]))
			},
		},
	}

	shanghaiTests := []testcase{
		{
			name:       "pre GALACTICA deploy 0xEF started contract code",
			code:       "",
			abi:        "",
			methodName: "",
			testFunc: func(ctx *context, t *testing.T) {
				code, _ := hex.DecodeString("60ef60005360016000f3")

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.NoFork).
					PrepareClause(tx.NewClause(nil).WithData(code), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()

				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)
			},
		},
		{
			name:       "GALACTICA deploy 0xEF started contract code",
			code:       "",
			abi:        "",
			methodName: "",
			testFunc: func(ctx *context, t *testing.T) {
				failingCalldata := []string{"60ef60005360016000f3", "60ef60005360026000f3", "60ef60005360036000f3", "60ef60005360206000f3"}
				for _, calldata := range failingCalldata {
					code, _ := hex.DecodeString(calldata)

					exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.ForkConfig{}).
						PrepareClause(tx.NewClause(nil).WithData(code), 0, math.MaxUint64, &xenv.TransactionContext{})
					out, _, err := exec()

					assert.Nil(t, err)
					assert.NotNil(t, out.VMErr)
					assert.Equal(t, "invalid code: must not begin with 0xef", out.VMErr.Error())
				}
			},
		},
		{
			name:       "GALACTICA deploy None 0xEF started contract code",
			code:       "",
			abi:        "",
			methodName: "",
			testFunc: func(ctx *context, t *testing.T) {
				code, _ := hex.DecodeString("60ee60005360016000f3")

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(nil).WithData(code), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()

				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)
			},
		},
		{
			name:       "GALACTICA PUSH0 gas cost",
			code:       "",
			abi:        "",
			methodName: "",
			testFunc: func(ctx *context, t *testing.T) {
				// 0x5f is PUSH0 opCode
				codeData := []byte{byte(vm.PUSH0)}
				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(nil).WithData(codeData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()

				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)
				assert.Equal(t, uint64(2), math.MaxUint64-out.LeftOverGas)
			},
		},
		{
			name:       "GALACTICA BASEFEE output gas cost",
			code:       hex.EncodeToString([]byte{byte(vm.BASEFEE)}),
			abi:        "",
			methodName: "",
			testFunc: func(ctx *context, t *testing.T) {
				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{BaseFee: big.NewInt(thor.InitialBaseFee)}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(&target), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				assert.Equal(t, uint64(2), math.MaxUint64-out.LeftOverGas)
			},
		},
		{
			name: "GALACTICA BASEFEE",
			code: hex.EncodeToString(
				[]byte{byte(vm.BASEFEE), byte(vm.PUSH1), 0x80, byte(vm.MSTORE), byte(vm.PUSH1), 0x20, byte(vm.PUSH1), 0x80, byte(vm.RETURN)},
			),
			abi:        "",
			methodName: "",
			testFunc: func(ctx *context, t *testing.T) {
				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{BaseFee: big.NewInt(thor.InitialBaseFee)}, &thor.ForkConfig{}).
					PrepareClause(tx.NewClause(&target), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)
				assert.True(t, new(big.Int).SetBytes(out.Data).Cmp(big.NewInt(thor.InitialBaseFee)) == 0)
			},
		},
		{
			name:       "Precompile modexp",
			code:       "608060405234801561001057600080fd5b506004361061002b5760003560e01c8063577b75b914610030575b600080fd5b61004a60048036038101906100459190610287565b610060565b60405161005791906102e9565b60405180910390f35b6000806000600573ffffffffffffffffffffffffffffffffffffffff168460405161008b9190610375565b600060405180830381855afa9150503d80600081146100c6576040519150601f19603f3d011682016040523d82523d6000602084013e6100cb565b606091505b509150915081610110576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101079061040f565b60405180910390fd5b80806020019051810190610124919061045b565b92505050919050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101948261014b565b810181811067ffffffffffffffff821117156101b3576101b261015c565b5b80604052505050565b60006101c661012d565b90506101d2828261018b565b919050565b600067ffffffffffffffff8211156101f2576101f161015c565b5b6101fb8261014b565b9050602081019050919050565b82818337600083830152505050565b600061022a610225846101d7565b6101bc565b90508281526020810184848401111561024657610245610146565b5b610251848285610208565b509392505050565b600082601f83011261026e5761026d610141565b5b813561027e848260208601610217565b91505092915050565b60006020828403121561029d5761029c610137565b5b600082013567ffffffffffffffff8111156102bb576102ba61013c565b5b6102c784828501610259565b91505092915050565b6000819050919050565b6102e3816102d0565b82525050565b60006020820190506102fe60008301846102da565b92915050565b600081519050919050565b600081905092915050565b60005b8381101561033857808201518184015260208101905061031d565b60008484015250505050565b600061034f82610304565b610359818561030f565b935061036981856020860161031a565b80840191505092915050565b60006103818284610344565b915081905092915050565b600082825260208201905092915050565b7f4d6f64756c61724578706f6e656e74696174696f6e3a204661696c656420617460008201527f2063616c63756c6174696e672074686520726573756c74000000000000000000602082015250565b60006103f960378361038c565b91506104048261039d565b604082019050919050565b60006020820190508181036000830152610428816103ec565b9050919050565b610438816102d0565b811461044357600080fd5b50565b6000815190506104558161042f565b92915050565b60006020828403121561047157610470610137565b5b600061047f84828501610446565b9150509291505056fea2646970667358221220d362967bdc1ba52fd086eee3092f6a9a77f6993dd89c74e0718dfa791bc4794e64736f6c63430008180033",
			abi:        `[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"}],"name":"modExpBytes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`,
			methodName: "modExpBytes",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract Math{
				// 	function modExpBytes(bytes memory input) public view returns (uint256) {
				// 		(bool success, bytes memory result) = (address(5).staticcall(input));
				// 		require(success, "ModularExponentiation: Failed at calculating the result");
				// 		return abi.decode(result, (uint256));
				// 	}
				// }

				// test case picked from vm/testdata/precompiles
				input := common.FromHex(
					"000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
				)
				methodData, err := ctx.method.EncodeInput(input)
				if err != nil {
					t.Fatal(err)
				}

				forkConfig := thor.NoFork
				forkConfig.ETH_IST = 0

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasBefore := math.MaxUint64 - out.LeftOverGas

				forkConfig.GALACTICA = 0
				exec, _ = runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err = exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasAfter := math.MaxUint64 - out.LeftOverGas
				assert.True(t, gasBefore > gasAfter)
				// test case picked from vm/testdata/precompiles, 5580-1365
				assert.Zero(t, gasBefore-gasAfter-4215)
			},
		},
		{
			name:       "Precompile bn256Add",
			code:       "608060405234801561001057600080fd5b506004361061002b5760003560e01c806383304e8a14610030575b600080fd5b61004a60048036038101906100459190610287565b610060565b60405161005791906102e9565b60405180910390f35b6000806000600673ffffffffffffffffffffffffffffffffffffffff168460405161008b9190610375565b600060405180830381855afa9150503d80600081146100c6576040519150601f19603f3d011682016040523d82523d6000602084013e6100cb565b606091505b509150915081610110576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101079061040f565b60405180910390fd5b80806020019051810190610124919061045b565b92505050919050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101948261014b565b810181811067ffffffffffffffff821117156101b3576101b261015c565b5b80604052505050565b60006101c661012d565b90506101d2828261018b565b919050565b600067ffffffffffffffff8211156101f2576101f161015c565b5b6101fb8261014b565b9050602081019050919050565b82818337600083830152505050565b600061022a610225846101d7565b6101bc565b90508281526020810184848401111561024657610245610146565b5b610251848285610208565b509392505050565b600082601f83011261026e5761026d610141565b5b813561027e848260208601610217565b91505092915050565b60006020828403121561029d5761029c610137565b5b600082013567ffffffffffffffff8111156102bb576102ba61013c565b5b6102c784828501610259565b91505092915050565b6000819050919050565b6102e3816102d0565b82525050565b60006020820190506102fe60008301846102da565b92915050565b600081519050919050565b600081905092915050565b60005b8381101561033857808201518184015260208101905061031d565b60008484015250505050565b600061034f82610304565b610359818561030f565b935061036981856020860161031a565b80840191505092915050565b60006103818284610344565b915081905092915050565b600082825260208201905092915050565b7f626e3235364164643a204661696c65642061742063616c63756c6174696e672060008201527f74686520726573756c7400000000000000000000000000000000000000000000602082015250565b60006103f9602a8361038c565b91506104048261039d565b604082019050919050565b60006020820190508181036000830152610428816103ec565b9050919050565b610438816102d0565b811461044357600080fd5b50565b6000815190506104558161042f565b92915050565b60006020828403121561047157610470610137565b5b600061047f84828501610446565b9150509291505056fea26469706673582212203fcccf78a18856182dd27cee0e3011ad18f00a9a29feb40bbcf800cdeb29a75a64736f6c63430008180033",
			abi:        `[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"}],"name":"bn256AddBytes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`,
			methodName: "bn256AddBytes",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract Math{
				// 	function bn256AddBytes(bytes memory input) public view returns (uint256) {
				// 		(bool success, bytes memory result) = (address(6).staticcall(input));
				// 		require(success, "bn256Add: Failed at calculating the result");
				// 		return abi.decode(result, (uint256));
				// 	}
				// }
				// test case picked from vm/testdata/precompiles
				input := common.FromHex(
					"18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7",
				)
				methodData, err := ctx.method.EncodeInput(input)
				if err != nil {
					t.Fatal(err)
				}

				forkConfig := thor.NoFork
				forkConfig.ETH_IST = 0

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasBefore := math.MaxUint64 - out.LeftOverGas

				forkConfig.GALACTICA = 0
				exec, _ = runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err = exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasAfter := math.MaxUint64 - out.LeftOverGas
				assert.True(t, gasBefore > gasAfter)
				// test case picked from vm/testdata/precompiles, 500-150
				assert.Zero(t, gasBefore-gasAfter-350)
			},
		},
		{
			name:       "Precompile bn256ScalarMul",
			code:       "608060405234801561001057600080fd5b506004361061002b5760003560e01c8063701961aa14610030575b600080fd5b61004a60048036038101906100459190610274565b610060565b604051610057919061033c565b60405180910390f35b6060600080600773ffffffffffffffffffffffffffffffffffffffff168460405161008b919061039a565b600060405180830381855afa9150503d80600081146100c6576040519150601f19603f3d011682016040523d82523d6000602084013e6100cb565b606091505b509150915081610110576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161010790610434565b60405180910390fd5b8092505050919050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61018182610138565b810181811067ffffffffffffffff821117156101a05761019f610149565b5b80604052505050565b60006101b361011a565b90506101bf8282610178565b919050565b600067ffffffffffffffff8211156101df576101de610149565b5b6101e882610138565b9050602081019050919050565b82818337600083830152505050565b6000610217610212846101c4565b6101a9565b90508281526020810184848401111561023357610232610133565b5b61023e8482856101f5565b509392505050565b600082601f83011261025b5761025a61012e565b5b813561026b848260208601610204565b91505092915050565b60006020828403121561028a57610289610124565b5b600082013567ffffffffffffffff8111156102a8576102a7610129565b5b6102b484828501610246565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156102f75780820151818401526020810190506102dc565b60008484015250505050565b600061030e826102bd565b61031881856102c8565b93506103288185602086016102d9565b61033181610138565b840191505092915050565b600060208201905081810360008301526103568184610303565b905092915050565b600081905092915050565b6000610374826102bd565b61037e818561035e565b935061038e8185602086016102d9565b80840191505092915050565b60006103a68284610369565b915081905092915050565b600082825260208201905092915050565b7f626e3235364164643a204661696c65642061742063616c63756c6174696e672060008201527f74686520726573756c7400000000000000000000000000000000000000000000602082015250565b600061041e602a836103b1565b9150610429826103c2565b604082019050919050565b6000602082019050818103600083015261044d81610411565b905091905056fea2646970667358221220469c90049edd24983926f66efd6ae8e5ca288ad0b2b206fde2e8a5fb6875a81764736f6c63430008180033",
			abi:        `[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"}],"name":"bn256ScalarMulBytes","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"}]`,
			methodName: "bn256ScalarMulBytes",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract Math{
				// 	function bn256ScalarMulBytes(bytes memory input) public view returns (bytes memory) {
				// 		(bool success, bytes memory result) = (address(7).staticcall(input));
				// 		require(success, "bn256ScalarMul: Failed at calculating the result");
				// 		return result;
				// 	}
				// }
				// test case picked from vm/testdata/precompiles
				input := common.FromHex(
					"2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2",
				)
				methodData, err := ctx.method.EncodeInput(input)
				if err != nil {
					t.Fatal(err)
				}

				forkConfig := thor.NoFork
				forkConfig.ETH_IST = 0

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasBefore := math.MaxUint64 - out.LeftOverGas

				forkConfig.GALACTICA = 0
				exec, _ = runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err = exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasAfter := math.MaxUint64 - out.LeftOverGas
				assert.True(t, gasBefore > gasAfter)
				// test case picked from vm/testdata/precompiles, 40000-6000
				assert.Zero(t, gasBefore-gasAfter-34000)
			},
		},
		{
			name:       "Precompile bn256Pairing",
			code:       "608060405234801561001057600080fd5b506004361061002b5760003560e01c806382b72bfd14610030575b600080fd5b61004a60048036038101906100459190610287565b610060565b60405161005791906102e9565b60405180910390f35b6000806000600873ffffffffffffffffffffffffffffffffffffffff168460405161008b9190610375565b600060405180830381855afa9150503d80600081146100c6576040519150601f19603f3d011682016040523d82523d6000602084013e6100cb565b606091505b509150915081610110576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101079061040f565b60405180910390fd5b80806020019051810190610124919061045b565b92505050919050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101948261014b565b810181811067ffffffffffffffff821117156101b3576101b261015c565b5b80604052505050565b60006101c661012d565b90506101d2828261018b565b919050565b600067ffffffffffffffff8211156101f2576101f161015c565b5b6101fb8261014b565b9050602081019050919050565b82818337600083830152505050565b600061022a610225846101d7565b6101bc565b90508281526020810184848401111561024657610245610146565b5b610251848285610208565b509392505050565b600082601f83011261026e5761026d610141565b5b813561027e848260208601610217565b91505092915050565b60006020828403121561029d5761029c610137565b5b600082013567ffffffffffffffff8111156102bb576102ba61013c565b5b6102c784828501610259565b91505092915050565b6000819050919050565b6102e3816102d0565b82525050565b60006020820190506102fe60008301846102da565b92915050565b600081519050919050565b600081905092915050565b60005b8381101561033857808201518184015260208101905061031d565b60008484015250505050565b600061034f82610304565b610359818561030f565b935061036981856020860161031a565b80840191505092915050565b60006103818284610344565b915081905092915050565b600082825260208201905092915050565b7f626e32353650616972696e673a204661696c65642061742063616c63756c617460008201527f696e672074686520726573756c74000000000000000000000000000000000000602082015250565b60006103f9602e8361038c565b91506104048261039d565b604082019050919050565b60006020820190508181036000830152610428816103ec565b9050919050565b610438816102d0565b811461044357600080fd5b50565b6000815190506104558161042f565b92915050565b60006020828403121561047157610470610137565b5b600061047f84828501610446565b9150509291505056fea26469706673582212203f7c1d2240693474593d78d55b56a6b10de6fb80fd59956aea8108863cc779b064736f6c63430008180033",
			abi:        `[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"}],"name":"bn256PairingBytes","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"}]`,
			methodName: "bn256PairingBytes",
			testFunc: func(ctx *context, t *testing.T) {
				// pragma solidity >=0.7.0 <0.9.0;
				// contract Math{
				// contract Math{
				// 	function bn256PairingBytes(bytes memory input) public view returns (uint256) {
				// 		(bool success, bytes memory result) = (address(8).staticcall(input));
				// 		require(success, "bn256Pairing: Failed at calculating the result");
				// 		return abi.decode(result, (uint256));
				// 	}
				// }
				// test case picked from vm/testdata/precompiles
				input := common.FromHex(
					"1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
				)
				methodData, err := ctx.method.EncodeInput(input)
				if err != nil {
					t.Fatal(err)
				}

				forkConfig := thor.NoFork
				forkConfig.ETH_IST = 0

				exec, _ := runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err := exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasBefore := math.MaxUint64 - out.LeftOverGas

				forkConfig.GALACTICA = 0
				exec, _ = runtime.New(ctx.chain, ctx.state, &xenv.BlockContext{}, &forkConfig).
					PrepareClause(tx.NewClause(&target).WithData(methodData), 0, math.MaxUint64, &xenv.TransactionContext{})
				out, _, err = exec()
				assert.Nil(t, err)
				assert.Nil(t, out.VMErr)

				gasAfter := math.MaxUint64 - out.LeftOverGas
				assert.True(t, gasBefore > gasAfter)
				// test case picked from vm/testdata/precompiles, 260000-113000
				assert.Zero(t, gasBefore-gasAfter-147000)
			},
		},
	}

	tests := []testcase{}

	tests = append(tests, baseTests...)
	tests = append(tests, shanghaiTests...)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			db := muxdb.NewMem()
			g := genesis.NewDevnet()
			stater := state.NewStater(db)
			b0, _, _, _ := g.Build(stater)
			repo, _ := chain.NewRepository(db, b0)

			ctx := &context{
				repo.NewChain(b0.Header().ID()),
				stater.NewState(trie.Root{Hash: b0.Header().StateRoot()}),
				nil,
			}

			if len(tt.methodName) > 0 {
				abi, _ := abi.New([]byte(tt.abi))
				method, _ := abi.MethodByName(tt.methodName)

				ctx.method = method
			}

			if len(tt.code) > 0 {
				code, err := hex.DecodeString(tt.code)
				if err != nil {
					t.Fatal(err)
				}
				ctx.state.SetCode(target, code)
			}

			tt.testFunc(ctx, t)
		})
	}
}

func TestPreForkOpCode(t *testing.T) {
	db := muxdb.NewMem()
	g := genesis.NewDevnet()
	stater := state.NewStater(db)
	b0, _, _, _ := g.Build(stater)
	repo, _ := chain.NewRepository(db, b0)

	chain := repo.NewChain(b0.Header().ID())
	state := stater.NewState(trie.Root{Hash: b0.Header().StateRoot()})

	tests := []struct {
		name string
		code []byte
		op   vm.OpCode
	}{
		{
			name: "BASEFEE",
			code: []byte{byte(vm.BASEFEE)},
			op:   vm.BASEFEE,
		},
		{
			name: "PUSH0",
			code: []byte{byte(vm.PUSH0)},
			op:   vm.PUSH0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			exec, _ := runtime.New(chain, state, &xenv.BlockContext{}, &thor.NoFork).
				PrepareClause(tx.NewClause(nil).WithData(tt.code), 0, math.MaxUint64, &xenv.TransactionContext{})
			out, _, err := exec()
			assert.Nil(t, err)
			assert.NotNil(t, out.VMErr)
			assert.Equal(t, fmt.Sprintf("invalid opcode 0x%x", int(tt.op)), out.VMErr.Error())

			// this one applies a fork config that forks from the start
			exec, _ = runtime.New(chain, state, &xenv.BlockContext{BaseFee: big.NewInt(thor.InitialBaseFee)}, &thor.ForkConfig{}).
				PrepareClause(tx.NewClause(nil).WithData(tt.code), 0, math.MaxUint64, &xenv.TransactionContext{})
			out, _, err = exec()
			assert.Nil(t, err)
			assert.Nil(t, out.VMErr, "after fork should not return error")
		})
	}
}

func TestCall(t *testing.T) {
	db := muxdb.NewMem()

	g := genesis.NewDevnet()
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)

	repo, _ := chain.NewRepository(db, b0)

	state := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})

	rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, &thor.NoFork)

	method, _ := builtin.Params.ABI.MethodByName("executor")
	data, err := method.EncodeInput()
	assert.Nil(t, err)

	exec, _ := rt.PrepareClause(
		tx.NewClause(&builtin.Params.Address).WithData(data),
		0, math.MaxUint64, &xenv.TransactionContext{})
	out, _, err := exec()
	assert.Nil(t, err)
	assert.Nil(t, out.VMErr)

	var addr common.Address
	err = method.DecodeOutput(out.Data, &addr)
	assert.Nil(t, err)

	assert.Equal(t, thor.Address(addr), genesis.DevAccounts()[0].Address)

	// contract NeverStop {
	// 	constructor() public {
	// 		while(true) {
	// 		}
	// 	}
	// }
	data, _ = hex.DecodeString(
		"6080604052348015600f57600080fd5b505b600115601b576011565b60358060286000396000f3006080604052600080fd00a165627a7a7230582026c386600e61384b3a93bf45760f3207b5cac072cec31c9cea1bc7099bda49b00029",
	)
	exec, interrupt := rt.PrepareClause(tx.NewClause(nil).WithData(data), 0, math.MaxUint64, &xenv.TransactionContext{})

	go func() {
		interrupt()
	}()

	out, interrupted, err := exec()

	assert.NotNil(t, out)
	assert.True(t, interrupted)
	assert.Nil(t, err)
}

func getMockTx(repo *chain.Repository, txType tx.Type, t *testing.T) *tx.Transaction {
	blockRef := tx.NewBlockRef(0)
	chainTag := repo.ChainTag()
	expiration := uint32(10)
	gas := uint64(210000)
	to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed")

	builder := tx.NewBuilder(txType).
		BlockRef(blockRef).
		ChainTag(chainTag).
		Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})).
		Clause(tx.NewClause(&to).WithValue(big.NewInt(20000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})).
		Expiration(expiration).
		Gas(gas)

	if txType == tx.TypeDynamicFee {
		builder.MaxFeePerGas(big.NewInt(thor.InitialBaseFee))
	}

	tx := builder.Build()

	sig, err := crypto.Sign(tx.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey)
	if err != nil {
		t.Fatal(err)
	}
	tx = tx.WithSignature(sig)

	return tx
}

func GetMockFailedTx(txType tx.Type) *tx.Transaction {
	to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed")
	return tx.NewBuilder(txType).ChainTag(1).
		BlockRef(tx.BlockRef{0, 0, 0, 0, 0xaa, 0xbb, 0xcc, 0xdd}).
		Expiration(32).
		Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})).
		Clause(tx.NewClause(&to).WithValue(big.NewInt(20000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})).
		GasPriceCoef(128).
		Gas(21000).
		DependsOn(nil).
		Nonce(12345678).Build()
}

func TestGetValues(t *testing.T) {
	db := muxdb.NewMem()

	g := genesis.NewDevnet()
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)

	repo, _ := chain.NewRepository(db, b0)

	state := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})
	rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, &thor.NoFork)

	runtimeChain := rt.Chain()
	runtimeState := rt.State()
	runtimeContext := rt.Context()

	assert.NotNil(t, runtimeChain)
	assert.NotNil(t, runtimeState)
	assert.NotNil(t, runtimeContext)
}

func TestExecuteTransactionFailure(t *testing.T) {
	origin := genesis.DevAccounts()[0]

	db := muxdb.NewMem()

	g := genesis.NewDevnet()
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)

	repo, _ := chain.NewRepository(db, b0)

	state := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})

	originEnergy := new(big.Int)
	originEnergy.SetString("9000000000000000000000000000000000000", 10)
	state.SetEnergy(origin.Address, originEnergy, 0)

	for _, txType := range []tx.Type{tx.TypeLegacy, tx.TypeDynamicFee} {
		tx := GetMockFailedTx(txType)

		rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, &thor.NoFork)

		_, err := rt.ExecuteTransaction(tx)

		assert.NotNil(t, err)
	}
}

func TestExecuteTransaction(t *testing.T) {
	db := muxdb.NewMem()
	fc := &thor.SoloFork
	hayabusaTP := uint32(math.MaxUint32)
	thor.SetConfig(thor.Config{HayabusaTP: &hayabusaTP})
	fc.HAYABUSA = math.MaxUint32
	g := genesis.NewDevnetWithConfig(genesis.DevConfig{
		ForkConfig: fc,
	})
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)
	repo, _ := chain.NewRepository(db, b0)
	st := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})
	ver := trie.Version{Major: b0.Header().Number() + 1, Minor: 0}
	stg, err := st.Stage(ver)
	assert.Nil(t, err)
	root, err := stg.Commit()
	assert.Nil(t, err)
	b1 := new(
		block.Builder,
	).ParentID(b0.Header().ID()).
		Timestamp(b0.Header().Timestamp() + thor.BlockInterval()).
		BaseFee(big.NewInt(thor.InitialBaseFee)).
		StateRoot(root).
		Build()
	repo.AddBlock(b1, nil, 0, true)

	maxFee := int64(thor.InitialBaseFee * 1000)
	maxPriorityFee := maxFee / 100
	gas := uint64(42000)
	dynTx := tx.NewBuilder(tx.TypeDynamicFee).
		ChainTag(repo.ChainTag()).
		Gas(gas).
		MaxFeePerGas(big.NewInt(maxFee)).
		MaxPriorityFeePerGas(big.NewInt(maxPriorityFee)).
		Build()
	dynTx = tx.MustSign(dynTx, genesis.DevAccounts()[0].PrivateKey)

	legacyTx := tx.NewBuilder(tx.TypeLegacy).
		ChainTag(repo.ChainTag()).
		Gas(gas).
		GasPriceCoef(128).
		Build()
	legacyTx = tx.MustSign(legacyTx, genesis.DevAccounts()[0].PrivateKey)

	t.Run("Receipt check with legacy tx before galactica fork", func(t *testing.T) {
		st := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})
		prevPayerEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()).Get(genesis.DevAccounts()[0].Address)
		assert.Nil(t, err)
		prevEndorsorEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()).Get(thor.Address{})
		assert.Nil(t, err)

		rt := runtime.New(repo.NewChain(b0.Header().ID()), st, &xenv.BlockContext{}, fc)
		receipt, err := rt.ExecuteTransaction(legacyTx)
		assert.Nil(t, err)

		// Assert
		assert.False(t, receipt.Reverted)
		assert.NotNil(t, receipt)

		currentPayerEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()+10).Get(genesis.DevAccounts()[0].Address)
		assert.Nil(t, err)
		currentEndorsorEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()+10).Get(thor.Address{})
		assert.Nil(t, err)

		// Expecting to consume just the intrinsic gas portion
		assert.Equal(t, gas-21000, receipt.GasUsed)

		// Payer
		balanceDelta := new(big.Int).Sub(prevPayerEnergy, currentPayerEnergy)
		assert.True(t, balanceDelta.Cmp(receipt.Paid) == 0)
		assert.True(t, receipt.Paid.Cmp(common.Big0) > 0)

		// Endorsor
		assert.True(t, currentEndorsorEnergy.Cmp(prevEndorsorEnergy) > 0)
		EndorsorEnergyDelta := new(big.Int).Sub(currentEndorsorEnergy, prevEndorsorEnergy)
		assert.True(t, EndorsorEnergyDelta.Cmp(receipt.Reward) == 0)
	})

	t.Run("Receipt check with legacy tx after galactica fork", func(t *testing.T) {
		st := state.New(db, trie.Root{Hash: b1.Header().StateRoot(), Ver: trie.Version{Major: b1.Header().Number(), Minor: 0}})
		prevPayerEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()).Get(genesis.DevAccounts()[0].Address)
		assert.Nil(t, err)
		prevEndorsorEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()).Get(thor.Address{})
		assert.Nil(t, err)

		rt := runtime.New(repo.NewChain(b0.Header().ID()), st, &xenv.BlockContext{BaseFee: big.NewInt(thor.InitialBaseFee)}, fc)
		receipt, err := rt.ExecuteTransaction(legacyTx)
		assert.Nil(t, err)

		// Assert
		assert.False(t, receipt.Reverted)
		assert.NotNil(t, receipt)

		currentPayerEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()+10).Get(genesis.DevAccounts()[0].Address)
		assert.Nil(t, err)
		currentEndorsorEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()+10).Get(thor.Address{})
		assert.Nil(t, err)

		// Expecting to consume just the intrinsic gas portion
		assert.Equal(t, gas-21000, receipt.GasUsed)

		// Payer
		balanceDelta := new(big.Int).Sub(prevPayerEnergy, currentPayerEnergy)
		assert.True(t, balanceDelta.Cmp(receipt.Paid) == 0)
		assert.True(t, receipt.Paid.Cmp(common.Big0) > 0)

		// Endorsor
		assert.True(t, currentEndorsorEnergy.Cmp(prevEndorsorEnergy) > 0)
		EndorsorEnergyDelta := new(big.Int).Sub(currentEndorsorEnergy, prevEndorsorEnergy)
		assert.True(t, EndorsorEnergyDelta.Cmp(receipt.Reward) == 0)
	})

	t.Run("Receipt check with dyn fee tx after galactica fork", func(t *testing.T) {
		st := state.New(db, trie.Root{Hash: b1.Header().StateRoot(), Ver: trie.Version{Major: b1.Header().Number(), Minor: 0}})
		prevPayerEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()).Get(genesis.DevAccounts()[0].Address)
		assert.Nil(t, err)
		prevEndorsorEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()).Get(thor.Address{})
		assert.Nil(t, err)

		rt := runtime.New(repo.NewChain(b0.Header().ID()), st, &xenv.BlockContext{BaseFee: big.NewInt(thor.InitialBaseFee)}, fc)
		receipt, err := rt.ExecuteTransaction(dynTx)
		assert.Nil(t, err)

		// Assert
		assert.False(t, receipt.Reverted)
		assert.NotNil(t, receipt)

		currentPayerEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()+10).Get(genesis.DevAccounts()[0].Address)
		assert.Nil(t, err)
		currentEndorsorEnergy, err := builtin.Energy.Native(st, b0.Header().Timestamp()+10).Get(thor.Address{})
		assert.Nil(t, err)

		// Expecting to consume just the intrinsic gas portion
		assert.Equal(t, gas-21000, receipt.GasUsed)

		// Payer
		balanceDelta := new(big.Int).Sub(prevPayerEnergy, currentPayerEnergy)
		assert.True(t, balanceDelta.Cmp(receipt.Paid) == 0)
		assert.True(t, receipt.Paid.Cmp(common.Big0) > 0)

		// Endorsor
		assert.True(t, currentEndorsorEnergy.Cmp(prevEndorsorEnergy) > 0)
		EndorsorEnergyDelta := new(big.Int).Sub(currentEndorsorEnergy, prevEndorsorEnergy)
		assert.True(t, EndorsorEnergyDelta.Cmp(receipt.Reward) == 0)
	})

	t.Run("Test mixed txs", func(t *testing.T) {
		st := state.New(db, trie.Root{Hash: b1.Header().StateRoot(), Ver: trie.Version{Major: b1.Header().Number(), Minor: 0}})
		txs := []*tx.Transaction{
			getMockTx(repo, tx.TypeLegacy, t),
			getMockTx(repo, tx.TypeDynamicFee, t),
		}

		for _, trx := range txs {
			rt := runtime.New(
				repo.NewChain(b1.Header().ID()),
				st,
				&xenv.BlockContext{Time: b1.Header().Timestamp(), BaseFee: b1.Header().BaseFee(), Number: b1.Header().Number()},
				&thor.SoloFork,
			)

			receipt, err := rt.ExecuteTransaction(trx)
			if err != nil {
				t.Fatal(err)
			}
			_ = receipt
		}
	})
}

func TestNoRewards(t *testing.T) {
	db := muxdb.NewMem()
	g := genesis.NewDevnet()
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)
	repo, _ := chain.NewRepository(db, b0)
	state := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})

	tests := []struct {
		name  string
		getTx func() *tx.Transaction
	}{
		{
			name: "Dyn tx with maxFeePerGas = 0",
			getTx: func() *tx.Transaction {
				dt := tx.NewBuilder(tx.TypeDynamicFee).
					ChainTag(repo.ChainTag()).
					Gas(21000).
					MaxFeePerGas(big.NewInt(thor.InitialBaseFee * 1000)).
					MaxPriorityFeePerGas(common.Big0).
					Build()
				return tx.MustSign(dt, genesis.DevAccounts()[0].PrivateKey)
			},
		},
		{
			name: "Dyn tx with maxFee equals to baseFee",
			getTx: func() *tx.Transaction {
				dt := tx.NewBuilder(tx.TypeDynamicFee).
					ChainTag(repo.ChainTag()).
					Gas(21000).
					MaxFeePerGas(big.NewInt(thor.InitialBaseFee)).
					MaxPriorityFeePerGas(big.NewInt(thor.InitialBaseFee)).
					Build()
				assert.Nil(t, err)
				return tx.MustSign(dt, genesis.DevAccounts()[0].PrivateKey)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			prevEndorsorEnergy, err := builtin.Energy.Native(state, b0.Header().Timestamp()).Get(thor.Address{})
			assert.Nil(t, err)

			rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{BaseFee: big.NewInt(thor.InitialBaseFee)}, &thor.ForkConfig{})
			receipt, err := rt.ExecuteTransaction(tt.getTx())

			// Assert
			assert.Nil(t, err)
			assert.False(t, receipt.Reverted)
			assert.NotNil(t, receipt)

			currentEndorsorEnergy, err := builtin.Energy.Native(state, b0.Header().Timestamp()+10).Get(thor.Address{})
			assert.Nil(t, err)

			// Endorsor gets no rewards
			assert.True(t, currentEndorsorEnergy.Cmp(prevEndorsorEnergy) == 0)
			assert.True(t, receipt.Reward.Cmp(common.Big0) == 0)
		})
	}
}

func TestExecuteTransactionPreHayabusa(t *testing.T) {
	origin := genesis.DevAccounts()[0]
	beneficiary := thor.Address{}

	db := muxdb.NewMem()

	g := genesis.NewDevnet()
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)

	repo, _ := chain.NewRepository(db, b0)

	state := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})

	beneficiaryEnergyBalance, err := builtin.Energy.Native(state, b0.Header().Timestamp()).Get(beneficiary)
	assert.NoError(t, err)
	assert.Equal(t, big.NewInt(0), beneficiaryEnergyBalance)

	originEnergy := new(big.Int)
	originEnergy.SetString("9000000000000000000000000000000000000", 10)
	state.SetEnergy(origin.Address, originEnergy, 0)

	tx := GetMockTx(repo, t)

	rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, &thor.NoFork)

	receipt, err := rt.ExecuteTransaction(tx)
	if err != nil {
		t.Fatal(err)
	}
	_ = receipt

	beneficiaryEnergyBalance, err = builtin.Energy.Native(state, b0.Header().Timestamp()).Get(beneficiary)
	assert.NoError(t, err)
	assert.Equal(t, receipt.Reward, beneficiaryEnergyBalance)

	expectedReward, _ := new(big.Int).SetString("11229600000000000000", 10)
	assert.Equal(t, expectedReward, beneficiaryEnergyBalance)
}

func TestExecuteTransactionAfterHayabusa(t *testing.T) {
	origin := genesis.DevAccounts()[0]
	beneficiary := thor.Address{}

	db := muxdb.NewMem()

	g := genesis.NewDevnet()
	b0, _, _, err := g.Build(state.NewStater(db))
	assert.Nil(t, err)

	repo, _ := chain.NewRepository(db, b0)

	state := state.New(db, trie.Root{Hash: b0.Header().StateRoot()})

	beneficiaryEnergyBalance, err := builtin.Energy.Native(state, b0.Header().Timestamp()).Get(beneficiary)
	assert.NoError(t, err)
	assert.Equal(t, big.NewInt(0), beneficiaryEnergyBalance)

	originEnergy := new(big.Int)
	originEnergy.SetString("9000000000000000000000000000000000000", 10)
	state.SetEnergy(origin.Address, originEnergy, 0)

	tx := GetMockTx(repo, t)
	fc := thor.NoFork
	fc.HAYABUSA = 0
	bc := &xenv.BlockContext{}
	bc.Number = 1

	rt := runtime.New(repo.NewChain(b0.Header().ID()), state, bc, &fc)

	receipt, err := rt.ExecuteTransaction(tx)
	if err != nil {
		t.Fatal(err)
	}
	_ = receipt

	beneficiaryEnergyBalance, err = builtin.Energy.Native(state, b0.Header().Timestamp()).Get(beneficiary)
	assert.NoError(t, err)
	assert.Equal(t, receipt.Reward, beneficiaryEnergyBalance)
}

func GetMockTx(repo *chain.Repository, t *testing.T) *tx.Transaction {
	blockRef := tx.NewBlockRef(0)
	chainTag := repo.ChainTag()
	expiration := uint32(10)
	gas := uint64(210000)
	to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed")

	tx := tx.NewBuilder(tx.TypeLegacy).
		BlockRef(blockRef).
		ChainTag(chainTag).
		Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})).
		Clause(tx.NewClause(&to).WithValue(big.NewInt(20000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})).
		Expiration(expiration).
		Gas(gas).
		Build()
	sig, err := crypto.Sign(tx.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey)
	if err != nil {
		t.Fatal(err)
	}
	tx = tx.WithSignature(sig)

	return tx
}
